import os, platform, shutil, subprocess
import config
from SCons.Script import *

deps = ['nsi', 'dmg', 'deb', 'rpm']


def write_single_var(stream, name, value, callback = None):
    if callback is not None: value = callback(value)

    stream.write('%s: %s\n' % (name, value))
    

def WriteVariable(self, env, stream, name, var, default = None, callback = None,
                  multi = False):
    if var in env: value = env.get(var)
    elif default is not None: value = default
    else: return

    if multi:
        values = filter(lambda x: x, map(str.strip, value.split(',')))
        for value in values: write_single_var(stream, name, value, callback)

    else: write_single_var(stream, name, value, callback)


def GetPackageType(env):
    if env.get('package_type'): return env.get('package_type')

    if env['PLATFORM'] == 'win32': return 'exe'
    elif env['PLATFORM'] == 'darwin': return 'dmg'
    elif env['PLATFORM'] == 'posix':
        dist = platform.dist()[0].lower()

        if dist in ['debian', 'ubuntu']: return 'deb'
        elif dist in ['centos', 'redhat', 'fedora']: return 'rpm'

        raise Exception, 'Unsupported Posix distribution ' + dist

    raise Exception, 'Unsupported package platform %s' % env['PLATFORM']


def GetPackageArch(env):
    if env.get('package_arch'): return env.get('package_arch')

    type = GetPackageType(env)

    if type == 'deb':
        proc = subprocess.Popen(['/usr/bin/dpkg', '--print-architecture'],
                                stdout = subprocess.PIPE)
        return proc.communicate()[0].strip()

    return platform.machine()
    

def GetPackageName(env, name, without_build = False):
    type = GetPackageType(env)

    if type == 'rpm': seps = ['-', '_']
    else: seps = ['_', '-']

    if env.get('version', False):
        name += seps[0] + env.get('version').replace(seps[0], seps[1])

    if env.get('package_build', False) and not without_build:
        name += seps[0] + env.get('package_build').replace(seps[0], seps[1])

    elif type == 'rpm': name += '-1' # Need release to build RPM

    arch = GetPackageArch(env)
    if arch:
        if type == 'rpm': name += '.' + arch
        elif type == 'deb': name += seps[0] + arch

    name += '.' + GetPackageType(env)

    return name



def find_files(path, type = None, ignores = None):
    if not os.path.exists(path): return

    if os.path.isdir(path):
        names = os.listdir(path)

        if ignores is not None:
            ignored_names = ignores(path, names)
            names = filter(lambda x: x in ignored_names, names)

        for name in names:
            for child in find_files(os.path.join(path, name), ignores):
                yield child

    if type is None: yield path
    elif type == 'f':
        if os.path.isfile(path): yield path
    elif type == 'd':
        if os.path.isdir(path): yield path
    elif type == 'l':
        if os.path.islink(path): yield path
    elif type == 'm':
        if os.path.ismount(path): yield path


def FindFiles(env, path, **kwargs):
    return find_files(path, **kwargs)


def resolve_file_map(sources, target, ignores = None):
    for src in sources:
        if isinstance(src, (list, tuple)) and len(src) == 2:
            src_path = src[0]
            dst_path = os.path.join(target, src[1])
        else:
            src_path = src
            dst_path = os.path.join(target, os.path.basename(src))

        if os.path.isdir(src_path):
            names = os.listdir(src_path)
            
            if ignores is not None:
                ignored_names = ignores(src_path, names)
                names = filter(lambda x: not x in ignored_names, names)

            names = map(lambda x: os.path.join(src_path, x), names)

            for pair in resolve_file_map(names, dst_path, ignores):
                yield pair

        else: yield [src_path, dst_path]


def ResolvePackageFileMap(env, sources, target):
    ignores = shutil.ignore_patterns(*env.get('package_ignores'))
    return resolve_file_map(sources, target, ignores)


def CopyToPackage(env, sources, target, perms = 0644, dperms = 0755):
    ignores = shutil.ignore_patterns(*env.get('package_ignores'))

    for src, dst in resolve_file_map(sources, target, ignores):
        print 'installing "%s" to "%s"' % (src, dst)

        dst_dir = os.path.dirname(dst)
        if not os.path.exists(dst_dir): os.makedirs(dst_dir, dperms)
        shutil.copy2(src, dst)
        os.chmod(dst, perms)


def WriteStringToFile(env, path, contents):
    f = None
    try:
        f = open(path, 'w')
        f.write(contents)
    finally:
        if f is not None: f.close()


def Packager(env, name, **kwargs):
    name = name.lower()

    # Setup env
    kwargs['package_name'] = name
    env = env.Override(kwargs)

    # Compute package name
    target = GetPackageName(env, name)

    # Write package.txt
    env.WriteStringToFile('package.txt', target)

    # Call OS specific package builder
    type = GetPackageType(env)
    if type == 'exe': return env.Nsis(target, kwargs['nsi'], **kwargs)
    elif type == 'dmg': return env.DMG(target, [], **kwargs)
    elif type == 'deb': return env.Deb(target, [], **kwargs)
    elif type == 'rpm': return env.RPM(target, [], **kwargs)


def add_vars(vars):
    vars.AddVariables(
        ('package_type', 'Override the package type'),
        ('package_build', 'Set package build name'),
        ('package_clean', 'Clean package build files', False),
        ('package_arch', 'Clean package architecture'),
        )


def configure(conf, ignores = ['.svn', '.sconsign.dblite',
                               '.sconf_temp', '*~', '*.o', '*.obj']):
    env = conf.env

    config.configure('nsi', conf)
    config.configure('dmg', conf)
    config.configure('deb', conf)
    config.configure('rpm', conf)

    AddMethod(Environment, FindFiles, 'FindFiles')
    AddMethod(Environment, WriteVariable, 'WriteVariable')
    AddMethod(Environment, GetPackageName, 'GetPackageName')
    AddMethod(Environment, GetPackageType, 'GetPackageType')
    AddMethod(Environment, GetPackageArch, 'GetPackageArch')
    AddMethod(Environment, CopyToPackage, 'CopyToPackage')
    AddMethod(Environment, ResolvePackageFileMap, 'ResolvePackageFileMap')
    AddMethod(Environment, WriteStringToFile, 'WriteStringToFile')
    AddMethod(Environment, Packager, 'Packager')

    env.SetDefault(package_ignores = ignores)

    return True
